#include "raytracer.h"

#include <QImage>
#include <QtCore/qmath.h>
#include <float.h>
#include "implicitsphere.h"

Raytracer::Raytracer(int width, int height, QObject *parent)
    : QThread(parent), width(width), height(height)
{
    image = new QImage(width, height, QImage::Format_RGB32);

    // hardcoded viewing volume with fovy and near
    setViewParameters(90.0, 1.0);

    rayEx = 0.0f;
    rayEy = 0.0f;
    rayEz = 0.0f;
}

Raytracer::~Raytracer()
{
    delete image;
}

void Raytracer::startRendering(IScene *scene)
{
    start();
}

void Raytracer::run()
{
    float x, y, z;
    float rayVx, rayVy, rayVz;

    z = rayEz - near;

    for (int xp = 0; xp < width; xp++)
    {
        for (int yp = 0; yp < height; yp++)
        {
            x = rayEx + (float) (xp) / (width - 1) * w - ( (float) w / 2.0f);
            y = rayEy + (float) ((height) - 1.0f - yp) / (height) * h - ( (float) h/ 2.0f);

            // ray vector
            rayVx = x - rayEx;
            rayVy = y - rayEy;
            rayVz = z - rayEz;

            QColor color = traceRayAndGetColor(rayEx, rayEy, rayEz, rayVx, rayVy, rayVz);

            if (color.isValid())
            {
                image->setPixel(xp, yp, color.rgb());
            }
        }

        Q_EMIT updateProgess(xp);
    }

    Q_EMIT renderingFinished(image);
}

float Raytracer::normalize(float vec[])
{
    float l = qSqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]);

    vec[0] = vec[0] / l;
    vec[1] = vec[1] / l;
    vec[2] = vec[2] / l;

    return l;
}

void Raytracer::setViewParameters(float fovyDegree, float near)
{
    // set attributes fovyDegree, fovyRadians, near
    fovyDegree = fovyDegree;
    this->near = near;
    fovyRadians = fovyDegree / 180 * 3.14;

    aspect = (double) width / height;

    // set attributes h, w
    h = 2 * near * qTan(fovyRadians / 2);
    w = h * aspect;
}

float Raytracer::calculateN(float fn[], float p1[], float p2[], float p3[])
{
    float ax, ay, az, bx, by, bz;

                // a = Vi2-Vi1, b = Vi3-Vi1
                ax = p2[0] - p1[0];
                ay = p2[1] - p1[1];
                az = p2[2] - p1[2];

                bx = p3[0] - p1[0];
                by = p3[1] - p1[1];
                bz = p3[2] - p1[2];



                // n =a x b

                fn[0]= ay*bz - az * by;
                fn[1]= az*bx - ax * bz;
                fn[2]= ax*by - ay * bx;

                // normalize n, calculate and return area of triangle
                return normalize(fn) / 2;
}


bool Raytracer::triangleTest(float p[], float p1[], float p2[], float p3[], float a, float ai[])
{
float* tmp = new float[3];

ai[0] = calculateN(tmp, p1, p2, p);
ai[1] = calculateN(tmp, p2, p3, p);
ai[2] = calculateN(tmp, p3, p1, p);

float ages = ai[0]+ai[1]+ai[2];

if ( qAbs(ages - a) < 1E-5)
    return true;

return false;
}


bool Raytracer::mashTest(float rayEx, float rayEy, float rayEz, float rayVx, float rayVy, float rayVz, float min[], float max[])
{

    // front
       float rayVn =  rayVz;
       float pen = max[2]-rayEz;
       float t = pen / rayVn;

       float* ip = new float[3];

       ip[0] = rayEx + t * rayVx;
       ip[1] = rayEy + t * rayVy;


       if (ip[0] < max[0] && ip[0] > min[0] && ip[1] < max[1] && ip[1] > min[1])
       {
           return true;
       }

       //right


        rayVn = rayVx ;
        pen = (max[0]-rayEx);
        t = pen / rayVn;

        ip[1] = rayEy + t * rayVy;
        ip[2] = rayEz + t * rayVz;

      if (  ip[1] < max[1] && ip[1] > min[1] && ip[2] < max[2] && ip[2] > min[2])
             return true;

        //left


        rayVn = -rayVx ;
        pen = -(min[0]-rayEx);
        t = pen / rayVn;

        ip[1] = rayEy + t * rayVy;
        ip[2] = rayEz + t * rayVz;

        if (ip[1] < max[1] && ip[1] > min[1] && ip[2] < max[2] && ip[2] > min[2])
             return true;

         //bottom

         pen = -(min[1]-rayEy);
         rayVn = -rayVy ;
         t = pen / rayVn;

         ip[0] = rayEx + t * rayVx;
         ip[2] = rayEz + t * rayVz;

          if (ip[0] < max[0] && ip[0] > min[0] && ip[2] < max[2] && ip[2] > min[2])
                 return true;


          //top

          pen = -(max[1]-rayEy);
          rayVn =-rayVy ;
          t = pen / rayVn;
          ip[0] = rayEx + t * rayVx;
          //ip[1] = rayEy + t * rayVy;
          ip[2] = rayEz + t * rayVz;

          if (ip[0] < max[0] && ip[0] > min[0] && ip[2] < max[2] && ip[2] > min[2])
                 return true;

          //bottom
          pen = -(min[2]-rayEz);
          rayVn = -rayVz;
          t = pen / rayVn;
          ip[0] = rayEx + t * rayVx;
          ip[1] = rayEy + t * rayVy;

             if (ip[0] < max[0] && ip[0] > min[0] && ip[1] < max[1] && ip[1] > min[1])
             {
                 return true;
             }


     return false;
 }
QColor Raytracer::traceRayAndGetColor(float rayEx, float rayEy, float rayEz, float rayVx, float rayVy, float rayVz)
{
    float minT = FLT_MAX;
    int minObjectsIndex = -1;
    int minIndex = -1;
    float minIP[3];
    float minN[3];
    float minMaterial[3];
    float minMaterialN = 1;
    float bu = 0, bv = 0, bw = 1;

    float v[3];
    float l[3];

    ImplicitSphere sphere;

    sphere.center[0] = 0.0f;
    sphere.center[1] = 0.0f;
    sphere.center[2] = -3.0f;

    sphere.radius = 1.0f;


    // intersection uses quadratic equation
    double emx, emy, emz;

    emx = rayEx - sphere.center[0];
    emy = rayEy - sphere.center[1];
    emz = rayEz - sphere.center[2];

    double ememScalar = emx * emx + emy * emy + emz * emz;
    double vvScalar = rayVx * rayVx + rayVy * rayVy + rayVz * rayVz;
    double vemScalar = rayVx * emx + rayVy * emy + rayVz * emz;

    // t*t * vvScalar + 2 * t * vemScalar + ememScalar - sphere.radius * sphere.radius;
    double a, p, q;

    a = vvScalar;
    p = (2 * vemScalar) / a;
    q = (ememScalar - sphere.radius * sphere.radius) / a;

    double d = p*p - 4*q;

    // no intersection point? => next object
    if (d <= 0)
        return QColor();

    // from here: intersection takes place!

    // calculate first intersection point with sphere along the ray
    // x1/2 = - p / 2 +- sqrt( (p/2)^2 - q )
    double p2 = p / 2.0;
    double t1 = - p2 + qSqrt(p2*p2 - q );
    double t2 = - p2 - qSqrt( p2*p2 - q );

    double t;

    if (t2 > t1)
        t = t1;
    else
        t = t2;

    // I'm the winner until now!
    minT = t;

    // prepare everything for phong shading

    // the intersection point s(t) = RayE + t * RayV;
    minIP[0] = rayEx + t * rayVx;
    minIP[1] = rayEy + t * rayVy;
    minIP[2] = rayEz + t * rayVz;

    // the normal vector at the intersection point, normalize!
    minN[0] = minIP[0] - sphere.center[0];
    minN[1] = minIP[1] - sphere.center[1];
    minN[2] = minIP[2] - sphere.center[2];

    normalize(minN);

    return QColor(255, 0, 0);

    // the material
    //minMaterial = sphere.material;
    //minMaterialN = sphere.materialN;

    return QColor();
}
